﻿using System;
using System.Collections.Generic;
using Microsoft.ApplicationServer.Caching;
using System.Threading;

namespace CacheHandlerManager
{
    //all cache handling for ASNet goes through here
    public class CacheHandler
    {
        private DataCache _cache;
        private Dictionary<string, DataCacheLockHandle> locks;
        private string hostName, cachePartition;
        private int cachePort;

        public CacheHandler(string hostName, int cachePort, string cachePartition)
        {
            this.locks = new Dictionary<string, DataCacheLockHandle>();
            this.hostName = hostName;
            this.cachePartition = cachePartition;
            this.cachePort = cachePort;

            initialiseCache();
        }

        private void initialiseCache()
        {
            DataCacheFactory _factory;
            List<DataCacheServerEndpoint> servers = new List<DataCacheServerEndpoint>(1);
            servers.Add(new DataCacheServerEndpoint(hostName, cachePort));
            DataCacheFactoryConfiguration config = new DataCacheFactoryConfiguration();
            config.Servers = servers;
            config.LocalCacheProperties = new DataCacheLocalCacheProperties();
            DataCacheClientLogManager.ChangeLogLevel(System.Diagnostics.TraceLevel.Off);

            _factory = new DataCacheFactory(config);
            _cache = _factory.GetCache(cachePartition);
            _cache.CreateRegion(cachePartition);
        }

        public object getWithLock(string key, int lockTime)
        {
            bool success = false;
            TimeSpan interval = new TimeSpan(0, 0, lockTime);
            DataCacheLockHandle lHandle;
            object r = null;

            //busy wait
            while (!success)
            {
                try
                {
                    r = _cache.GetAndLock(key, interval, out lHandle, cachePartition);
                    locks.Add(key, lHandle);
                    return r;
                }
                catch (DataCacheException e)
                {
                    if (e.ErrorCode != DataCacheErrorCode.ObjectLocked)
                    {
                        return null;
                    }
                }
            }
            return null;
        }

        public void putWithUnlock(string key, object val)
        {
            int retryAttempts = 5;
            DataCacheLockHandle lHandle;
            //the retry logic here attempts to fix a rare, intermittent exception when calling put causing the ASNetWin service errors
            while (retryAttempts > 0)
            {
                try
                {
                    if (locks.TryGetValue(key, out lHandle))
                    {
                        _cache.PutAndUnlock(key, val, lHandle, cachePartition);
                        locks.Remove(key);
                    }
                    else
                    {
                        _cache.Put(key, val, cachePartition);
                    }
                    retryAttempts = 0;
                }
                catch (DataCacheException e)
                {
                    //don't hit it 5 times in a millisecond..give it some time to recover..the poor cache..
                    Thread.Sleep(1000);

                    if (--retryAttempts == 0)
                    {
                        throw (e);
                    };
                    
                }
            }
        }

        public object getWithNoLock(string key)
        {
            return _cache.Get(key, cachePartition);
        }

        public object putWithNoLock(string key, object val)
        {
            return _cache.Put(key, val, cachePartition);
        }

        public void removeFromCache(string key)
        {
            _cache.Remove(key, cachePartition);
        }

        public IEnumerable<KeyValuePair<string, object>> getAllObjectsInRegion(string region)
        {
            return _cache.GetObjectsInRegion(region);
        }

        public void unlock(string key)
        {
            DataCacheLockHandle lHandle;
            if (locks.TryGetValue(key, out lHandle))
            {
                _cache.Unlock(key, lHandle, cachePartition);
            }
        }
    }
}
